/**
 * 
 */
package br.org.bertol.mestrado.engine.optimisation.pso;

import java.util.ArrayList;
import java.util.EnumMap;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.optimisation.Unit;

/**
 * @author contaqualquer
 * 
 */
/**
 * @author contaqualquer
 *
 */
/**
 * @author contaqualquer
 */
public class Particle extends Unit {

    private ArrayList<Velocity>           speed;

    // melhor caminho percorrido
    private ArrayList<Integer>            pbest;

    // peso do melhor caminho percorrido
    // pela part�cula
    protected EnumMap<Objective, Integer> hpbest;

    // melhor caminho global
    private ArrayList<Integer>            gbest;

    // peso do melhor caminho global
    private EnumMap<Objective, Integer>   hgbest;

    private final LinearInertiaReduction  linearInertiaReduction;

    private final float                   cognitiveFactor;

    private final float                   socialFactor;

    private final float                   constrictionFactor;

    public Particle(final LinearInertiaReduction linearInertiaReduction,
        final float cognitiveFactor, final float socialFactor) {
        super();

        speed = new ArrayList<Velocity>();

        pbest = new ArrayList<Integer>();

        hpbest = new EnumMap<Objective, Integer>(Objective.class);

        gbest = new ArrayList<Integer>();

        hgbest = new EnumMap<Objective, Integer>(Objective.class);

        // caso os fatores de multiplica��o sejam negativos
        // multiplica todos por -1
        this.linearInertiaReduction = linearInertiaReduction;

        this.cognitiveFactor = cognitiveFactor < 0 ? cognitiveFactor * -1
                : cognitiveFactor;

        this.socialFactor = socialFactor < 0 ? socialFactor * -1 : socialFactor;

        final float t = (float) (2 / (2 - (this.socialFactor + this.cognitiveFactor) - Math
                .sqrt(Math.pow((this.socialFactor + this.cognitiveFactor), 2)
                - (4 * (this.socialFactor + this.cognitiveFactor)))));

        this.constrictionFactor = t < 0 ? t * -1 : t;
    }

    /**
     * 
     */
    @SuppressWarnings("unchecked")
    public final void updatePBest() {
        // atualiza caminho
        pbest = (ArrayList<Integer>) (route).clone();

        // atualiza peso
        hpbest = height.clone();
    }

    /**
     * 
     */
    @SuppressWarnings("unchecked")
    public final void updateGbest() {
        gbest = (ArrayList<Integer>) (pbest).clone();

        // atualiza peso
        hgbest = hpbest.clone();
    }

    /**
     * @param particle
     */
    @SuppressWarnings("unchecked")
    final void updateGbest(final Particle particle) {
        gbest = (ArrayList<Integer>) particle.getRoute().clone();

        hgbest = particle.getHeight().clone();
    }

    /**
     * Calcula a velocidade da part�cula.
     * @param kIteration
     */
    @SuppressWarnings("unchecked")
    final void updateVelocity(final int kIteration) {
        final ArrayList<Velocity> GBestSpeed = (ArrayList<Velocity>) multiplication(
                                                                                    gbest,
                                                                                    route,
                                                                                    socialFactor)
                .clone();

        final ArrayList<Velocity> PBestSpeed = (ArrayList<Velocity>) multiplication(
                                                                                    pbest,
                                                                                    route,
                                                                                    cognitiveFactor)
                .clone();

        final ArrayList<Velocity> V_x_inertia = (ArrayList<Velocity>) multiplication(
                                                                                     speed,
                                                                                     linearInertiaReduction
                                                                                             .get(kIteration))
                .clone();

        GBestSpeed.trimToSize();

        PBestSpeed.addAll(GBestSpeed);

        PBestSpeed.trimToSize();

        V_x_inertia.addAll(PBestSpeed);

        V_x_inertia.trimToSize();

        // inverte a ordem
        // Collections.reverse(V_x_inertia);

        // atualiza velocidade
        speed = multiplication(V_x_inertia, constrictionFactor);

    }

    /**
     * Realiza multiplica��o do array de velocidade com a in�rcia.
     * @param speed
     * @param factor
     * @return
     */
    private ArrayList<Velocity> multiplication(final ArrayList<Velocity> speed,
            final float factor) {
        // caso fator de multiplica��o seja 0, retorna
        if (factor == 0) {
            return new ArrayList<Velocity>(0);
        }

        // entre 0 e 1, truca o array de velocidades
        if (factor < 1 && factor > 0) {
            return multiplicationLower(speed, factor);
        }

        return multiplicationUpper(speed, factor);
    }

    /**
     * Realiza a multiplica��o do array dos arrays de PBest/GBest com a posi��o
     * da ppart�cula e os fatores sociais/cognitivos.
     * @param left
     * @param right
     * @param factor
     * @return
     */
    private ArrayList<Velocity> multiplication(final ArrayList<Integer> left,
            final ArrayList<Integer> right, final float factor) {
        // caso fator de multiplica��o seja 0, retorna
        if (factor == 0) {
            return new ArrayList<Velocity>(0);
        }

        final Float multiplicationFactor = factor * (float) Math.random();

        // entre 0 e 1, truca o array de velocidades
        if (multiplicationFactor < 1 && multiplicationFactor > 0) {
            return multiplicationLower(subtraction(left, right),
                                       multiplicationFactor);
        }

        return multiplicationUpper(subtraction(left, right),
                                   multiplicationFactor);
    }

    /**
     * Multiplica arrays com fatores negativos.
     * @param speedArray
     * @param multiplicationFactor
     * @return
     */
    private ArrayList<Velocity> multiplicationLower(
            final ArrayList<Velocity> speedArray,
            final float multiplicationFactor) {
        // trunca o array em quest�o, retornando o array truncado
        final Float truncate_array = multiplicationFactor * speedArray.size();

        final ArrayList<Velocity> trucatedSpeed = new ArrayList<Velocity>(
                speedArray.subList(0, truncate_array.intValue()));

        return trucatedSpeed;
    }

    /**
     * Multiplica arrays com fatores positivos.
     * @param speedArray
     * @param multiplicationFactor
     * @return
     */
    private ArrayList<Velocity> multiplicationUpper(
            final ArrayList<Velocity> speedArray,
            final Float multiplicationFactor) {
        final ArrayList<Velocity> newSpeedArray = new ArrayList<Velocity>(0);

        // parte decimal do array de velocidades
        final Float decimal = multiplicationFactor
                - multiplicationFactor.intValue();

        // soma o array com as partes inteiras
        for (int i = 0; i < multiplicationFactor.intValue(); i++) {
            newSpeedArray.addAll(speedArray);
        }

        // adiciona a parte fracion�ria
        newSpeedArray.addAll(multiplicationLower(newSpeedArray, decimal));

        return newSpeedArray;
    }

    /**
     * Recebe as listas de velocidade e pbest ou gbest, dependendo da situa��o e
     * monta novo array de velocidade para c�lculo.
     * @param array1
     * @param array2
     * @return
     */
    private ArrayList<Velocity> subtraction(final ArrayList<Integer> array1,
            final ArrayList<Integer> array2) {
        final ArrayList<Velocity> newVelocity = new ArrayList<Velocity>(0);

        final int pairSize = array1.size();

        // verifica itera o array da direita at� o seu final
        for (int left_index = 1; left_index <= pairSize; left_index++) {
            // pega item do array da esquerda
            final int left_item = array1.indexOf(left_index);

            final int right_index = array2.get(left_item);

            if (left_index != right_index) {
                newVelocity.add(new Velocity(left_index, right_index));
            }
        }

        return newVelocity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String toString() {
        final StringBuilder buffer = new StringBuilder();

        buffer.append(super.toString());

        buffer.append("Resultado").append("[").append(hpbest.toString())
                .append("]");

        return buffer.toString();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public final Object clone() throws CloneNotSupportedException {
        Particle clone = (Particle) super.clone();

        clone.pbest = (ArrayList<Integer>) this.pbest.clone();

        clone.gbest = (ArrayList<Integer>) this.gbest.clone();

        clone.speed = (ArrayList<Velocity>) this.speed.clone();

        clone.hpbest = this.hpbest.clone();

        clone.hgbest = this.hgbest.clone();

        return clone;
    }

    @Override
    public final void update() {
        updatePBest();
    }

    /**
     * @return the hbest
     */
    public final EnumMap<Objective, Integer> getHpbest() {
        return hpbest;
    }

    /**
     * @return the speed
     */
    final ArrayList<Velocity> getSpeed() {
        return speed;
    }
}
